package session

import (
	"fmt"
	"log"
	"sync"
	"sync/atomic"
	"time"

	"golang.org/x/net/websocket"
)

var _sess sync.Map
var _sessID int32

func Get(id string) *Session {
	s, ok := _sess.Load(id)
	if !ok {
		return nil
	}
	return s.(*Session)
}

func Register(s *Session) {
	_sess.Store(s.ID, s)
}

func UnRegister(id string) {
	_sess.Delete(id)
}

type Session struct {
	ID      string
	die     chan struct{}
	in      chan []byte
	out     chan []byte
	conn    *websocket.Conn
	loginAt time.Time
}

func New(conn *websocket.Conn) *Session {
	id := atomic.AddInt32(&_sessID, 1)
	sess := &Session{
		ID:      fmt.Sprintf("user%d", id),
		die:     make(chan struct{}),
		in:      make(chan []byte, 32),
		out:     make(chan []byte, 512),
		conn:    conn,
		loginAt: time.Now(),
	}
	//in queue
	go sess.inTask()
	//out queue
	go sess.outTask()

	return sess
}

func (s *Session) Send(data []byte) error {
	select {
	case s.out <- data:
	default:
		fmt.Printf("send queue is full , drop message data: %s", string(data))
	}
	return nil
}

func (s *Session) rawSend(data []byte) error {
	s.conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
	err := websocket.Message.Send(s.conn, data)
	if err != nil {
		fmt.Println("send error : ", err)
	}
	return err
}

func (s *Session) outTask() {
	defer func() {
		if x := recover(); x != nil {
			log.Println(x)
		}
	}()
	for {
		select {
		case data, ok := <-s.out:
			if !ok {
				return
			}
			s.rawSend(data)
		case <-s.die:
			return
		}

	}
}

func (s *Session) Receive(data []byte) {
	select {
	case s.in <- data:
	}
}

func (s *Session) inTask() {
	defer func() {
		if x := recover(); x != nil {
			log.Println(x)
		}
	}()

	for {
		select {
		case data, ok := <-s.in:
			if !ok {
				return
			}
			rst, err := dispather(s, data)
			if err != nil {
				fmt.Println("error: ", err)
				return
			}
			if rst != nil {
				s.Send(rst)
			}
		case <-s.die:
			return
		}

	}
}

func (s *Session) Close() {
	select {
	case <-s.die:
		return
	default:
		close(s.die)
	}
	close(s.in)
	close(s.out)
	s.conn.Close()
}

func BroadCast(data []byte) {
	_sess.Range(func(k interface{}, v interface{}) bool {
		s, ok := v.(*Session)
		if ok {
			s.Send(data)
		}
		return false
	})
}
